<?php

class SundayMountainGoods {

    // 请求指定URL的API并返回解析后的HTML内容
    public function requestApi($url, $good_id){

        # 定义HTTP请求头信息
        $aHeader = [
            'Accept-Language: ja,zh-CN;q=0.8,en-US;q=0.5,en;q=0.3',
            'Connection: keep-alive',
            'Host: www.sora-store.jp',
            'Accept-Encoding: gzip, deflate, br',
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0'
        ];

        $times = 1;// 尝试次数
        do {
            $ch = curl_init(); // 初始化cURL会话
            curl_setopt($ch, CURLOPT_URL, $url); // 设置请求URL
            //curl_setopt($ch, CURLOPT_TIMEOUT, 30); 
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader); // 设置HTTP请求头
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// 将cURL返回的内容作为字符串接收
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);// 允许cURL自动重定向
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// 禁用SSL验证
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);// 禁用主机SSL验证
            curl_setopt($ch, CURLOPT_ENCODING, 'gzip');// 设置接受gzip编码的响应
            $html = curl_exec($ch);// 执行cURL请求并获取响应            
            curl_close($ch);// 关闭cURL会话
            $times++;// 增加尝试次数
        }while(empty($html) && $times < 6);// 如果响应为空且尝试次数小于6，继续尝试

        $response = $this->parseHtml($html);// 解析HTML响应
        return $response;// 返回解析结果
    }

    // 解析HTML内容，提取价格和库存信息
    private function parseHtml($html){
        $res = [];// 初始化结果数组

        if(empty($html)) return $res;// 如果HTML为空，返回空数组
        // https://php.golaravel.com/domdocument.getelementbyid.html    php手册网页。

        // 创建DOMDocument对象并加载HTML内容
        $document = new \DOMDocument();
        @$document->loadHTML($html);
        $xpath = new DOMXPath($document);

        # 提取商品材料信息material
        $res['material'] = '';
        $materialsNode = $xpath->query('//b[text()="[MATERIALS]"]')->item(0);

        if ($materialsNode) {
            $materialsContent = '';

            for ($node = $materialsNode->nextSibling; $node; $node = $node->nextSibling) {
                if ($node->nodeType === XML_ELEMENT_NODE && strpos($node->textContent, '[SIZE]') !== false) {
                    break;
                }
                if ($node->nodeType === XML_TEXT_NODE || $node->nodeType === XML_ELEMENT_NODE) {
                    $materialsContent .= $node->textContent;
                }
            }

            $materialsContent = trim($materialsContent);
            $res['material'] = $materialsContent;
        }
        # 提取商品材料信息结束

        # 提取商品ID信息goodsId
        $res['goodsId'] = '';
        $xpath = new DOMXPath($document);
        $elements = $xpath->query('//*[@data-product-id]');
        # var_dump($elements);
        if ($elements->length > 0){
            $res['goodsId'] = $elements->item(0)->getAttribute('data-product-id');
        }
        # 提取商品ID信息结束

        # 提取商品名称goodsName
        $res['goodsName'] = '';
        $goods_id_elements = $xpath->query('//span[contains(@class, "fs-c-productNameHeading__name")]');
        # 输出提取的文本信息
        if($goods_id_elements->length > 0){
            $res['goodsName'] = trim($goods_id_elements->item(0)->textContent);
        }
        # 提取商品名称结束

        # DOM树的提取内容信息和上面用xpath提取的信息一样，就是xpath方法可以直接定位到信息所在的位置，将信息提取出来，而DOM树则需要一层一层的对信息进行解剖，需要详细的知道一层一层的元素，并且需要加入判断，而且不能直接访问子孙节点，需要通过子节点一层一层的访问，
            # 1. 其中getElementById()，()中需要填的是id的取值。这个方法是获取网页中特定的Id的取值下的元素信息。
            # 2. getElementsByTagName()方法，()中需要填的是例如div，span，这种的HTML标签的名称，这个方法则是获取Id为特定值中的特定的HTML标签中的信息。
            # 3. getAttribute()，()中需要填的是在当前结构下，想要获取的属性名的信息，这三个
            # 4. 然后这时候需要来个for循环对特定的HTML标签进行遍历，找到class属性为某个特定值元素中的特定值。这个值，才是我们最终需要的。
            #5. 其中1和2和3他们三个是配套使用的，因为1是找值，2是将1找到的东西全部查出来，3是进行匹配，然后再继续1，2，3，一层一层的匹配，一层一层的进，有多少层，就有多少个for循环，我感觉这样不行。
        # 不得不说，这个方法是真麻烦啊，主要是得一层一层的找，中间隔一层都找不到，这样写的时候就不能看源代码写了，就得看检查写，因为id是唯一的，只要有一个唯一标识，我们们就可以从这个唯一标识中往下一层一层的找，其中就需要一层一层的嵌套for循环。
        # 而xpath方法
            # 优点1：则可以直接定位到需要查找信息的位置下，提取当前位置的信息，但是需要注意的是，根据class的值查找的话，要看一下class的值是否是唯一的，如果不是唯一的，则需要定位到上一层级，从上一层级进入到这个层，然后看一下这两个层级下是不是唯一的，如果是，则可以进行匹配。
            # 缺点就是容易出现语法错误，但是也还好。有GPT在，也没有什么错误。
        # 使用DOM树进行提取goodsName2
        $res['goodsName2'] = '';
        // 获取id为fs_form的元素
        $fsForm = $document->getElementById("fs_form");

        if ($fsForm) {
            // 获取所有div标签
            $nodes2 = $fsForm->getElementsByTagName("div");
            foreach ($nodes2 as $node2) {
                // 查找特定类名的div标签
                if (stripos($node2->getAttribute('class'), 'fs-l-productLayout') !== false) {
                    $nodes3 = $node2->getElementsByTagName("div");
                    foreach ($nodes3 as $node3) {
                        if (stripos($node3->getAttribute('class'), 'fs-l-productLayout__item') !== false) {
                            $nodes4 = $node3->getElementsByTagName("h1");
                            foreach ($nodes4 as $node4) {
                                if (stripos($node4->getAttribute('class'), 'fs-c-productNameHeading') !== false) {
                                    $nodes5 = $node4->getElementsByTagName('span');
                                    foreach ($nodes5 as $node5){
                                        if(strpos($node5->getAttribute('class'), 'fs-c-productNameHeading__name') !== false){
                                            $goodsName2 = trim($node5->nodeValue);
                                            if (!empty($goodsName2)) {
                                                $res['goodsName2'] = $goodsName2;
                                            }
                                        }
                                    }
                                    
                                }
                            }
                        }
                    }
                }
            }
        } else {
            echo "Element with id 'fs_form' not found.\n";
        }
        # 使用DOM树对goodName提取结束

        # 提取商品商标brand
        $res['brand'] = '';
        $elements = $xpath->query('//text()[contains(.,"ブランド：")]/following-sibling::a[1]');
        if ($elements->length > 0){
            $res['brand'] = trim($elements->item(0)->textContent);
        }
        # 提取商品商标结束

        # 提取商品属性category
        $res['category'] = '';
        $category_elements = $xpath->query('//li[@class="fs-c-breadcrumb__listItem"]');
        // 提取每一个<li>中的内容
        $category_liContents = [];
        if($category_elements->length > 0){
            foreach ($category_elements as $element){
                $category_liContents[] = trim($element->textContent);
            }
        }
        # var_dump($category_liContents);
        $res['category'] = implode('######', $category_liContents);
        # 提取商品属性结束

        # 提取商品的网址信息url
        $res['url'] = '';
        $metas = $xpath->query('//meta[@property="og:url"]');
        foreach ($metas as $meta) {
            $res['url'] = $meta->getAttribute('content');
        }

        #提取商品网址信息结束

        # 提取商品图片信息imageUrl
        $res['imageUrl'] = [];
        $imageUrl_elements = $xpath->query('//figure[contains(@class, "fs-c-productThumbnail__image")]');
        // 套取每一个figure的内容
        $imageUrl_figureContents = [];
        if($imageUrl_elements->length > 0){
            # 套取figure下的img的属性值
            foreach ($imageUrl_elements as $element){
                $img_element = $element->getElementsByTagName('img')->item(0);
                # 套取img下的src的属性值
                if ($img_element){
                    $src = $img_element->getAttribute('src');
                    # 将获取的src的值保存到数组中
                    $imageUrl_figureContents[] = $src;
                }
                
            }
        }
        $res['imageUrl'] = $imageUrl_figureContents;
        # 提取商品图片信息结束

        # 提取商品内容信息content
        $res['content'] = '';
        $content_elements = $xpath->query('//div[contains(@class, "fs-p-productDescription")]');
        if ($content_elements->length > 0) {
            $html = $document->saveHTML($content_elements->item(0)); // 获取整个内容区域的HTML内容
            // 移除HTML标签
            $text = strip_tags($html);
            // 替换特殊字符
            $text = preg_replace('/◆/', '', $text);
            $text = preg_replace('/■/', '', $text);            
            // 获取 [SIZE] 之前的内容
            if (preg_match('/^(.*?)\[SIZE\]/s', $text, $matches)) {
                $res['content'] = trim($matches[1]);
            }
        }
        # 提取商品内容信息结束

        # 提取商品尺寸信息sizeInfo
        $res['size_Info'] = '';
        $table_sizeInfo = $xpath->query('//table[@class="sizetable"]');
        if ($table_sizeInfo->length > 0){
            $sizeInfo = $document->saveHTML($table_sizeInfo->item(0));
            $res['size_Info'] = $sizeInfo;
        }
        # 提取商品尺寸信息结束     

        # 提取商品性别信息sex
        $res['sex'] = '';
        $sex_content = '';
        $sex_elements = $xpath->query('//div[contains(@class, "fs-l-productLayout__item--2")]');
        if ($sex_elements->length > 0){
            $man_elements = $xpath->query('.//span[contains(@class, "fs-c-productMark__mark--27")]',$sex_elements->item(0));
            if ($man_elements->length > 0){
                $sex_content = $sex_content . "". "MAN,";
            }
            $woman_elements = $xpath->query('.//span[contains(@class, "fs-c-productMark__mark--41")]',$sex_elements->item(0));
            if ($woman_elements->length > 0){
                $sex_content = $sex_content . "". "WOMAN,";
            }
            $kid_elements = $xpath->query('.//span[contains(@class, "fs-c-productMark__mark--34")]',$sex_elements->item(0));
            if ($kid_elements->length > 0){
                $sex_content = $sex_content . "". "KID,";
            }
            $res['sex'] = $sex_content;
        }
        # 提取商品性别信息结束

        // 提取价格信息price
        $res['price'] = $res['old_price'] = 0;
        $stocks = [];// 初始化库存数组
        // 获取ID为"fs_form"的元素并遍历其子节点
        $nodes = $document->getElementById("fs_form")->getElementsByTagName("div");
        foreach ($nodes as $node) {

            // 原价
            // stripos用于查找子字符串在字符串中首次出现的位置，
            if(stripos($node->getAttribute('class'),'fs-c-productPrice--listed')!==false){
                $spans = $node->getElementsByTagName("span");
                foreach ($spans as $span) {
                    if($span->getAttribute('class') == 'fs-c-price__value'){
                        # trim方法用于移除字符串两端的空白字符, 将span->nodeValue中的一些东西替换成''
                        $price_value = trim(str_replace(['¥','&yen;','&#165;',',','￥','(税込)'], '', $span->nodeValue));
                        //echo $price_value;exit;
                        if(!empty($price_value)) $res['old_price'] = $price_value;
                    }
                }
            }

            // 现价
            if(stripos($node->getAttribute('class'),'fs-c-productPrice--selling')!==false){
                $spans = $node->getElementsByTagName("span");
                foreach ($spans as $span) {
                    //print_r($span);exit;
                    if($span->getAttribute('class') == 'fs-c-price__value'){
                        $price_value = trim(str_replace(['¥','&yen;','&#165;',',','￥','(税込)'], '', $span->nodeValue));
                        //echo $price_value;exit;
                        if(!empty($price_value)) $res['price'] = $price_value;
                    }
                }
            }
            # 提取价格信息结束

            // 提取库存信息sku
            if($node->getAttribute('class') == 'fs-c-variationList__item__body'){

                $tmps = $node->getElementsByTagName("li");
                foreach ($tmps as $tmp) {
                    $sku_id = '';
                    $sku_tmps = $tmp->getElementsByTagName("div");
                    foreach ($sku_tmps as $sku_tmp) {
                        if(strpos($sku_tmp->getAttribute('class'), 'fs-c-variationCart__cartButton')!==false){
                            $skuIdI = $sku_tmp->getAttribute('data-product-id');
                            $skuIdV = $sku_tmp->getAttribute('data-vertical-variation-no');
                            if(empty($skuIdV)){
                                $skuIdV = '0';
                            }
                            $skuIdH = $sku_tmp->getAttribute('data-horizontal-variation-no');
                            if(empty($skuIdH)){
                                $skuIdH = '0';
                            }
                            $sku_id = $skuIdI.'h'.$skuIdH.'v'.$skuIdV;
                        }
                    }
                    //echo $skuId;exit;

                    if(!empty($sku_id)){
                        $sku_price = 0;
                        $sku_stock = 999;
                        $teps = $tmp->getElementsByTagName("span");
                        foreach ($teps as $tep) {
                            if(strpos($tep->getAttribute('class'), 'fs-c-variationCart__variationName__stock')!==false){
                                $stockStr = $tep->nodeValue;

                                $sku_stock = 0;
                                if(strpos($stockStr, '残りわずか')!==false){
                                    $sku_stock = 2;
                                }
                            }

                            if(strpos($tep->getAttribute('class'), 'fs-c-price__value')!==false){
                                $sku_price = trim(str_replace([',','&yen;','&#165;','￥','(税込)'], '', $tep->nodeValue));
                            }
                        }

                        if($sku_stock > 0){
                            $stocks[$sku_id] = ['stock'=>$sku_stock, 'price'=>$sku_price];
                        }
                    }
                }
            }
        }
        //print_r($res);exit;
        // 如果未找到原价但找到了现价，设置原价为现价
        if($res['old_price'] == 0 && $res['price'] > 0) $res['old_price'] = $res['price'];

        // 如果找到库存信息，设置结果数组的库存信息，否则设置状态为1
        if(!empty($stocks)) $res['stocks'] = $stocks;
        else $res['status'] = 1;
        # 提取库存信息结束
        print_r($res);
        //print_r($res);exit;
        return $res; // 返回解析结果
    }
}

// 测试代码
$url = 'https://www.sora-store.jp/c/item/goods/other-goods/23BGNNNB42320S';
$good_id = 1;
$obj = new SundayMountainGoods();
$obj->requestApi($url, $good_id);